4.2-Promise 和 async
Create by fall on 23 Sep 2020 Recently revised in 27 Feb 2023
Promise
Promise 的思想是,先承诺好解决方案,如果正确,采用正确的解决方案,如果错误,采用错误的解决方案
如何执行的?
Promise 被调用后,它会以处理中状态开始,以被解决状态或被拒绝状态结束。
- 处理中状态:此时调用的函数会继续执行,直到解决为止,从而为调用的函数提供所请求的数据。
- 结束状态:被创建的 Promise 最终会以被**解决状态(fulfill)或被拒绝状态(reject)**结束,并在完成时调用相应的回调函数(解决状态传给
then
拒绝状态传给catch
)。
Promise 是用来管理异步编程的,它本身不是异步的,Promise 用于防止陷入回调地狱。
了解 Promise 的工作方式是了解
async
&await
的基础,因为这些异步函数在底层使用了promise
那些 JS API 使用了 Promise?
- Battery API
- Fetch API
- Service Worker
使用
var p = new Promise(function(resolve, reject) {
if (/* 条件 */Math.radom()>0.2) { // 符合条件,执行成功的函数resolve
resolve(/* result */);
} else {
// 不满足条件,执行 reject
reject(/* reason */);
}
})
// 对 Promise 的处理
// 方法一:p.then()内传入两个方法,
// 第一个方法执行时,使用 resolve 中的参数
// 第二个方法执行时,使用 resolve 中的参数
p.then((result)=>{
/* 成功后的执行内容 */
},(reason)=>{
/* 失败后的执行内容 */
})
// 方法二:成功的内容通过.then()获取。失败的内容也可以通过 .catch() 进行捕获
p.then(result=>{console.log(result)})
.catch((err)=>{throw new Error('错误')})
// 如果捕获错误中出现错误,可以添加第二个 catch 来补获
.catch((err)=>{console.log(err)}
Promise 中的错误会像冒泡一样,如果出现一个错误,接下来的 then 都不会执行。但可以利用这一特性——在最后使用 .catch 对于错误的处理,实现简化错误处理。
注:实现该特性需要注意,调用 then 时只传入一个函数作为参数,传入第二个函数相当于错误处理了。
是否执行
resolve
,reject
都是取决于是否执行resolve()
&reject()
或者是函数本身出现异常
嵌套调用
const pr = new Promise((resolve) => {
setTimeout(() => {
console.log(999)
}, 1000)
// 可以将 resolve 放到 setTimeout 里面进行尝试
resolve('666')
})
pr.then((result) => {
console.log("1111"+result)
return Promise.resolve("bbbb")
// 上一行 等价于
// return new Promise(resolve=>{
// resolve("bbbb")
// })
// 如果是一个普通的返回值,可以通过 then 接收普通值
// 返回值为 new Promise,会调用下一个 then 进行链式处理
})
.then((result) => {
console.log('吃人的东西'+result)
})
.catch((err) => {
console.log('出错了,玛德' + err)
})
当执行的函数出现错误时
new Promise(resolve=>{
resolve(a)
// 由于 a 没有定义,所以执行错误返回
}).then(result=>{
console.log("成功"+result)
},reason=>{
console.log("失败"+reason)
}).then(result=>{
console.log("success"+result)
},reason=>{
console.log('failure')
})
// 该函数第一次执行失败,调用失败的函数,输出原因
// 第二次由于没有返回值,以成功执行计算
// 第一个 then 判断的是 new Promise 对象执行成功与否,第二个 then 用来判断第一个 then 执行的成功与否
Promise 能解决回调地狱的问题,但是由于充满了 then 方法,使得可读性不强,所以使用
async & await
作为语法糖,很多人认为async & await
就是回调地狱的最终解决方案。
静态方法
Promise.resolve()
& Promise.reject()
Promise.resolve()
返回一个给定值解析后的 Promise 对象Promise.reject()
返回一个带拒绝原因的 Promise 对象
Promise.resolve('object')
// 等价于 new Promise(resolve => resolve("object")) 新建了一个执行成功,并且返回 object 的 Promise 对象
Promise.reject('do not handle this')
注:调用
reject
和resolve
之后 Promise 的使命就完成了,因此,操作不应该放在Promise
对象中,而应该放置在then
中执行。
Promise.all()
当返回的数组中所有的 Promise 对象的返回值都为 resolve 时,才会执行相应的 Promise,有一个出错,其他的 Promise 也无法执行
let p1 = Promise.resolve(1)
let p2 = Promise.resolve(2)
Promise.all([ p2, p1])
.then(result => {
// 返回的结果是按照 Array 中编写实例的顺序来
console.log(result) // [ 2, 1 ]
})
.catch(reason => {
console.log("失败:reason")
})
Promise.allSettled()
ES2020 引入的
allSettled
方法用于确保所有操作都结束假设一个页面有三个区域,分别对应三个独立的接口数据,使用
Promise.all
来并发请求三个接口,如果其中任意一个接口出现异常,状态是 reject ,这会导致页面中该三个区域数据全都无法出来,Promise.allSettled
的出现就可以解决这个问题。
let p1 = Promise.resolve(1)
let p2 = Promise.resolve(2)
Promise.allS([ p2, p1])
.then(result => {
// 返回的结果是按照 Array 中编写实例的顺序来
console.log(result) // [ 2, 1 ]
})
.catch(reason => {
console.log("失败:reason")
})
Promise.race()
Promise.race
只要有一个 promise 对象最先进入FulFilled
或者reject
状态,就会将这个内容进行返回。
function timerPromisefy(delay) {
return new Promise(resolve => {
setTimeout(() => {
resolve(delay);
}, delay);
});
}
// 任何一个 promise 率先变为 resolve 或 reject 的话程序就停止运行
Promise.race([
timerPromisefy(1),
timerPromisefy(32),
timerPromisefy(64)
]).then(function (value) {
console.log(value); // => 1
});
Promise.finally()
无论执行成功或者失败与否,都会在最后执行这个 finally 中的函数
this.loading = true
request()
.then((res) => {
// do something
})
.catch((err) => {
// output err
})
.finally(() => {
this.loading = false // 无论成功与否,都想把 loading 转换为 false
})
Promise.any()
传入一个可迭代的 Promise
对象,有任意一个状态为 fulfills(完成),则返回该内容,当所有的可迭代内容都返回 reject 后,才返回失败。
Promise.any([request('api1'),request('api2'),request('api3')])
.then((res) => {
// 任何一个完成,执行
})
.catch((err) => {
// 所有请求都出现问题
})
async & await
两个关于 JS 转译比较基础的概念:
- 转译器(transpilers)对语法的转换,如控制合并符
??
,babel 是最著名的转译器。- 垫片(polyfills)更新或添加最新的方法(API)的脚本被称为“polyfill”,core.js 支持了很多特性。
在这里,async 是特殊语法,所以需要通过 babel 转译语法,async 需要用到 Promise 所以需要 core.js 提供 Promise。
特点
- 语法简洁,更像是同步代码,也更符合普通的阅读习惯
- 改进 JS 中异步操作串行执行的代码组织方式,减少 callback 的嵌套
- Promise 中不能自定义使用
try/catch
进行错误捕获,但是在async/await
中可以像处理同步代码处理错误。
await 意思是,等待 await 里面的函数全部执行完成,再接着执行该 async 函数。并且在接着执行该函数之前,先执行其他函数。
async/await
的实现是基于 Promise的,async
关键字意味着该函数会返回 promise
,是 generator 的语法糖。
注意:await 将异步代码改造成了同步代码,这会导致多个异步代码中,如果没有依赖性却使用了 await 进行等待,会导致性能上的降低。
关键字作用
async
是 AsyncFunction 特性 中的关键字。
将 async
放在函数声明前面可以声明 async
函数
- await 会创建一个 Promise 对象,将任务提交给微任务队列;
- 暂停当前协程的执行,将主线程的控制权力转交给父协程执行,同时将 Promise 对象返回给父协程,继续执行父协程;
- 父协程执行结束之前会检查微任务队列,微任务队列中有 resolve(xxx) 等待执行,触发 then 的回调函数;
- 回调函数被激活后,会将主线程的控制权交给协程,继续执行后续语句,完成后将控制权还给父协程。
// async 怎么用?
async function getSomething() {
return "something";
}
// 或者使用箭头函数进行声明
let getSomething = async()=>{
return "something"
}
// 无论是函数式声明还是箭头函数,都会返回Promise对象
getSomething() // Promise { <state>: "fulfilled", <value>: "something" }
示例
const fun1 = async ()=>{
return '测试'
}
fun1().then(alert)
// fun1 等价于 fun2
const fun2 = ()=>{
return Promise.resolve('测试')
}
只有 await 右边返回为 resolve 才会继续执行,否则直接中断
let p1 = Promise.reject(100)
async function fn1() {
let result = await p1
console.log(1) // 这行代码不会执行
}
async 函数的执行顺序
let p1 = Promise.resolve(1)
let p2 = new Promise(resolve=>{
setTimeout(()=>{
resolve(2)
},2000)
})
async function fun(){
console.log(1)
let resu2 = await p1 // 等待返回 resolve ,返回 resolve 后继续执行
console.log(3)
let resu1 = await p2
console.log(4)
}
fun()
console.log(2)
任务队列(宏任务)
基于微任务的技术有
MutationObserver
、Promise
以及以Promise
为基础开发出来的很多其他的技术。不管宏任务是否到达时间,以及放置的先后顺序,每次主线程执行栈为空的时候,引擎会优先处理微任务队列,处理完微任务队列里的所有任务,再去处理宏任务。
根据 HTML5 的标准,setTimeout()
最少为 4 ms
顶层 await
可以在顶层,不在 function 包裹时,使用 await,但是需要保证文件为 .mjs
// say this is index.mjs
// to top-level await
await Promise.resolve('🍎') // '🍎'
宏任务和微任务
更多内容,可见 node 的事件循环
- 消息队列中的任务为宏任务。渲染进程内部会维护多个消息队列,比如延时执行队列和普通消息队列,主线程采用 for 循环,不断地从这些任务队列中取出任务并执行;
- 微任务是一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前;
- V8 在执行 javascript 脚本时,会为其创建一个全局执行上下文,同时会创建一个微任务队列;
- 执行微任务过程中产生的微任务不会推迟到下个宏任务中执行,而是在当前宏任务中继续执行;
内容参考
作者 | 文章链接 |
---|---|
边城 | https://segmentfault.com/a/1190000007535316 |